package net.towee.server.factories;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.util.List;

import javax.inject.Inject;
import javax.inject.Named;

import net.towee.model.bittorrent.TorrentData;
import net.towee.model.discussion.DiscussionFactory;
import net.towee.model.repo.AttachmentFactory;
import net.towee.model.repo.AttachmentIdentity;
import net.towee.model.repo.AttachmentInfo;
import net.towee.model.repo.AttachmentPicture;
import net.towee.model.repo.AttachmentPictureIdentity;
import net.towee.model.repo.AttachmentTorrent;
import net.towee.model.repo.AttachmentTorrentIdentity;
import net.towee.model.repo.AttachmentType;
import net.towee.model.repo.impl.AttachmentIdentityImpl;
import net.towee.model.repo.impl.AttachmentInfoImpl;
import net.towee.model.utils.Pair;
import net.towee.server.CoreSettings;
import net.towee.server.bittorrent.TorrentFactory;
import net.towee.server.persistence.AttachmentStore;
import net.towee.server.persistence.AttachmentStore.AttachmentStreamer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.drew.imaging.ImageMetadataReader;
import com.drew.imaging.ImageProcessingException;
import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import com.drew.metadata.MetadataException;
import com.drew.metadata.exif.ExifDescriptor;
import com.drew.metadata.exif.ExifDirectory;
import com.google.common.base.Preconditions;

/**
 * Implementation of attachment factory. It create attachment data by specified
 * file type
 * 
 * @author c58
 */
public class AttachmentFactoryImpl implements AttachmentFactory {
	private static final Logger LOG = LoggerFactory.getLogger(AttachmentFactoryImpl.class);

	private final AttachmentStore attStore;

	private final DiscussionFactory discFactory;
	private final TorrentFactory torrFactory;

	private final List<String> videoExts;
	private final List<String> audioExts;
	private final List<String> picExts;
	private final List<String> docExts;
	private final List<String> arcExts;

	private final String announceUrl;

	@Inject
	public AttachmentFactoryImpl(AttachmentStore store, DiscussionFactory discFactory,
			TorrentFactory torrFactory, @Named(CoreSettings.ANNOUNCE_URL) String announceUrl,
			@Named(CoreSettings.VIDEO_EXTS) List<String> videoExt,
			@Named(CoreSettings.AUDIO_EXTS) List<String> audioExt,
			@Named(CoreSettings.PIC_EXTS) List<String> picExt,
			@Named(CoreSettings.DOC_EXTS) List<String> docExt,
			@Named(CoreSettings.ARCHIVE_EXTS) List<String> arcExt) {

		this.attStore = store;
		this.torrFactory = torrFactory;
		this.discFactory = discFactory;
		this.announceUrl = announceUrl;

		this.videoExts = videoExt;
		this.audioExts = audioExt;
		this.picExts = picExt;
		this.docExts = docExt;
		this.arcExts = arcExt;
	}

	@Override
	public AttachmentInfo createAttachment(String id) {
		Preconditions.checkNotNull(id, "null attatchment id");
		
		// Get file from db
		AttachmentStreamer att = attStore.getAttachment(id.trim()); 
		if (att == null)
			return null;

		// Get file type
		AttachmentType type = getAttachmentType(att.getFileName());
		Pair<AttachmentInfo, AttachmentIdentity> data = null;

		// Get file information
		switch (type) {
		case VIDEO:
			data = getAttachmentVideo(att);
			break;
		case AUDIO:
			data = getAttachmentAudio(att);
			break;
		case PICTURE:
			data = getAttachmentPicture(att);
			break;
		case TORRENT:
			data = getAttachmentTorrent(att);
			break;
		case ARCHIVE:
			data = getAttachmentArchive(att);
			break;
		case DOCUMENT:
			data = getAttachmentDoc(att);
			break;
		}

		// If data is zero use default attachment data containers
		if (data == null) {
			AttachmentIdentity attIdnt = new AttachmentIdentityImpl(id, att.getFileName(),
					att.getContentSize(), type);
			AttachmentInfo attData = new AttachmentInfoImpl(attIdnt,
					discFactory.createDiscussion());
			data = new Pair<AttachmentInfo, AttachmentIdentity>(attData, attIdnt);
		}

		// Save attachment data and discussion
		attStore.saveAttachmentIdentity(data.getSecond());
		attStore.saveAttachmentData(data.getFirst());

		return data.getFirst();
	}

	// TODO: get some information about this formats
	private Pair<AttachmentInfo, AttachmentIdentity> getAttachmentDoc(AttachmentStreamer att) {
		return null;
	}

	private Pair<AttachmentInfo, AttachmentIdentity> getAttachmentArchive(AttachmentStreamer att) {
		return null;
	}

	private Pair<AttachmentInfo, AttachmentIdentity> getAttachmentVideo(AttachmentStreamer att) {
		return null;
	}

	private Pair<AttachmentInfo, AttachmentIdentity> getAttachmentAudio(AttachmentStreamer att) {
		return null;
	}

	/**
	 * Get information about picture;
	 * 
	 * @param att
	 * @return
	 */
	private Pair<AttachmentInfo, AttachmentIdentity> getAttachmentPicture(AttachmentStreamer att) {
		try {
			Metadata md = ImageMetadataReader.readMetadata(new BufferedInputStream(att.getInputStream()));
			Directory dir = md.getDirectory(ExifDirectory.class);
			ExifDescriptor descr = new ExifDescriptor(dir);

			// Get image size
			int width = 0, height = 0;
			height = dir.getInt(ExifDirectory.TAG_EXIF_IMAGE_HEIGHT);
			width = dir.getInt(ExifDirectory.TAG_EXIF_IMAGE_WIDTH);

			// Create attachment objects
			AttachmentPictureIdentity identity = new AttachmentIdentityImpl(att.getId(),
					att.getFileName(), att.getContentSize(), AttachmentType.PICTURE).toPicture();
			identity.setPictureSize(width, height);

			AttachmentPicture pic = new AttachmentInfoImpl(identity,
					discFactory.createDiscussion()).toPicture();

			// Set camera name
			pic.setCamera(dir.getString(ExifDirectory.TAG_MODEL));

			// Set flash state
			int val = dir.getInt( ExifDirectory.TAG_FLASH );
			pic.setFlash( (val & 0x1) != 0 ? true : false);
			
			// Set creation date
			pic.setCreationDate( dir.getDate(ExifDirectory.TAG_DATETIME) );
			pic.setDiaphragm( descr.getFNumberDescription() );
			pic.setExposure( descr.getExposureTimeDescription() );
			pic.setFocus( descr.getFocalLengthDescription() );
			pic.setWidth( width );
			pic.setHeight( height );

			// Set ISO number
			pic.setISO( dir.getInt(ExifDirectory.TAG_ISO_EQUIVALENT) );
			return new Pair<AttachmentInfo, AttachmentIdentity>(pic, identity);

		} catch (ImageProcessingException e) {
			LOG.error("Error while process image: ", e.getMessage());
		} catch (IOException e) {
			LOG.error("Error while process image: ", e.getMessage());
		} catch (MetadataException e) {
			LOG.error("Error while process image: ", e.getMessage());
		}

		return null;
	}

	/**
	 * Pair of attachment data and identity for torrent
	 * 
	 * @param att
	 * @return
	 */
	private Pair<AttachmentInfo, AttachmentIdentity> getAttachmentTorrent(AttachmentStreamer att) {
		try {
			// Create and register torrent
			TorrentData torrent = torrFactory.createTorrent(att.getInputStream(), att.getId());

			// Create identity
			AttachmentTorrentIdentity identity = new AttachmentIdentityImpl(att.getId(),
					att.getFileName(), att.getContentSize(), AttachmentType.TORRENT)
					.toTorrent();
			identity.setContentSize(torrFactory.getContentSize());

			// Create attachmend data
			AttachmentTorrent data = new AttachmentInfoImpl(identity,
					discFactory.createDiscussion()).toTorrent();
			
			data.setTorrentData( torrent );
			data.setItemsList( torrFactory.getItemsList() );

			return new Pair<AttachmentInfo, AttachmentIdentity>(data, identity);
		} catch (IOException e) {
			LOG.error("Error while getting input stream of attachment!", e);
		}

		return null;
	}

	/**
	 * Get file type by extension
	 * 
	 * @param fileName
	 * @return type of document
	 */
	private AttachmentType getAttachmentType(String fileName) {
		int pos;
		if ((pos = fileName.lastIndexOf('.')) < 0)
			return AttachmentType.OTHER;

		String ext = fileName.substring(pos + 1).toLowerCase();
		if (videoExts.contains(ext))
			return AttachmentType.VIDEO;
		if (audioExts.contains(ext))
			return AttachmentType.AUDIO;
		if (picExts.contains(ext))
			return AttachmentType.PICTURE;
		if (docExts.contains(ext))
			return AttachmentType.DOCUMENT;
		if (arcExts.contains(ext))
			return AttachmentType.ARCHIVE;
		if (ext.equals("torrent"))
			return AttachmentType.TORRENT;

		return AttachmentType.OTHER;
	}

}
